package com.hx.jvm.proxy.cglib.aop;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author jxlgcmh
 * @date 2020-08-16 21:44
 * @description Cglib 容器
 */
public class CglibContainer {
    // 通知类型
    public static enum InterceptPoint {
        BEFORE, AFTER, EXCEPTION
    }

    // 切面类型
    static Class<?>[] aspects = new Class<?>[]{ServiceLogAspect.class, ExceptionAspect.class};

    // 方法拦截的map
    static Map<Class<?>, Map<InterceptPoint, List<Method>>> InterceptMethodMap = new HashMap<>();

    //静态代码块初始化
    static {
        init();
    }

    // 初始化aop容器
    private static void init() {
        for (Class<?> clazz : aspects) {
            Aspect aspect = clazz.getAnnotation(Aspect.class);
            if (aspect != null) {
                Method before = getMethod(clazz, "before", new Class<?>[]{Object.class, Method.class, Object[].class});
                Method after = getMethod(clazz, "after", new Class<?>[]{Object.class, Method.class, Object[].class, Object.class});
                Method exception = getMethod(clazz, "exception", new Class<?>[]{Object.class, Method.class, Object[].class, Throwable.class});
                Class<?>[] interceptedArr = aspect.value();
                for (Class<?> intercepted : interceptedArr) {
                    addInterceptMethod(intercepted, InterceptPoint.BEFORE, before);
                    addInterceptMethod(intercepted, InterceptPoint.AFTER, after);
                    addInterceptMethod(intercepted, InterceptPoint.EXCEPTION, exception);
                }
            }
        }
    }

    // 添加拦截方法
    private static void addInterceptMethod(Class<?> clazz, InterceptPoint point, Method method) {
        if (method == null) {
            return;
        }
        Map<InterceptPoint, List<Method>> map = InterceptMethodMap.get(clazz);
        if (map == null) {
            map = new HashMap<>();
            InterceptMethodMap.put(clazz, map);
        }
        List<Method> methods = map.get(point);
        if (methods == null) {
            methods = new ArrayList<>();
            map.put(point, methods);
        }
        methods.add(method);
    }

    // 获取方法
    private static Method getMethod(Class<?> clazz, String name, Class<?>[] paramTypes) {
        try {
            return clazz.getMethod(name, paramTypes);
        } catch (NoSuchMethodException e) {
            return null;
        }
    }

    // 获取拦截方法
    static List<Method> getInterceptMethods(Class<?> clazz, InterceptPoint point) {
        Map<InterceptPoint, List<Method>> map = InterceptMethodMap.get(clazz);
        if (map == null) {
            return Collections.emptyList();
        }
        List<Method> methods = map.get(point);
        if (methods == null) {
            return Collections.emptyList();
        }
        return methods;
    }

    // 调用拦截方法
    static class AspectInterceptor implements MethodInterceptor {

        @Override
        public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            // 执行before方法
            List<Method> beforeMethods = getInterceptMethods(target.getClass().getSuperclass(), InterceptPoint.BEFORE);
            for (Method beforeMethod : beforeMethods) {
                beforeMethod.invoke(null, target, method, args);
            }

            try {
                // 调用原始方法
                Object result = methodProxy.invokeSuper(target, args);

                // 执行after方法
                List<Method> afterMethods = getInterceptMethods(target.getClass().getSuperclass(), InterceptPoint.AFTER);
                for (Method afterMethod : afterMethods) {
                    afterMethod.invoke(null, target, method, args, result);
                }
                return result;
            } catch (Throwable e) {
                List<Method> exceptionMethods = getInterceptMethods(target.getClass().getSuperclass(), InterceptPoint.EXCEPTION);
                for (Method exceptionMethod : exceptionMethods) {
                    exceptionMethod.invoke(null, target, method, args, e);
                }
                throw e;
            }
        }
    }

    @SuppressWarnings("unchecked")
    private static <T> T createInstance(Class<T> clazz) throws Exception {
        if (!InterceptMethodMap.containsKey(clazz)) {
            return (T) clazz.newInstance();
        }
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(new AspectInterceptor());
        return (T) enhancer.create();
    }

    public static <T> T getInstance(Class<T> clazz) {
        try {
            T instance = createInstance(clazz);
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(SimpleInject.class)) {
                    if (!field.isAccessible()) {
                        field.setAccessible(true);
                    }
                    Class<?> type = field.getType();
                    field.set(instance, getInstance(type));
                }
            }
            return instance;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}
